//
//  Creator.cpp
//  RenderV1
//
//  Created by 黄翔 on 2019/10/20.
//  Copyright © 2019 黄翔. All rights reserved.
//
#include "Creator.h"
#include "CombineInstance.h"

using namespace std;
using namespace hxe;

hxe::Mesh* hxe::Creator::createSphereMesh(float radius, unsigned int row, unsigned int col)
{
    vector<Vector3>* vertices = new vector<Vector3>();
    vector<Vector3>* normals = new vector<Vector3>();
    vector<Vector2>* uv = new vector<Vector2>();
    vector<unsigned int>* indices = new vector<unsigned int>();
    
    float rowRadian = glm::pi<float>() / (row - 1);
    float colRadian = glm::two_pi<float>() / col;
    
    for (unsigned int i = 0; i < col; i++)
    {
        float alpha = i * colRadian;
        
        for (unsigned int j = 0; j < row; j++)
        {
            float beta = j * rowRadian;
            Vector3 v;
            v.x = radius * glm::sin(beta)* glm::cos(alpha);
            v.y = radius * glm::cos(beta);
            v.z = radius * glm::sin(beta) * glm::sin(alpha);

            vertices->push_back(v);
            
            Vector2 u(0.0f, 0.0f);
            uv->push_back(u);
        }
    }
    
    for (unsigned int i = 0; i < col - 1; i++)
    {
        for (unsigned int j = 0; j < row - 1; j++)
        {
            indices->push_back((i + 1) * row + j);
            indices->push_back(i * row + j + 1);
            indices->push_back(i * row + j);

            indices->push_back((i + 1) * row + j + 1);
            indices->push_back(i * row + j + 1);
            indices->push_back((i + 1) * row + j);
        }
    }

    // 首尾相接
    for (unsigned int j = 0; j < row - 1; j++)
    {
        unsigned int colStart = (col - 1) * row;
        unsigned i1 = colStart + j;
        unsigned i2 = i1 + 1;
        unsigned i3 = j;
        unsigned i4 = i3 + 1;

        indices->push_back(i3);
        indices->push_back(i2);
        indices->push_back(i1);

        indices->push_back(i2);
        indices->push_back(i3);
        indices->push_back(i4);
    }
    
    genNormals(normals, vertices, indices);

    return new Mesh(vertices, normals, uv, indices);
}

hxe::GameObject* hxe::Creator::createSphere(float radius, unsigned int row, unsigned int col)
{
    GameObject* sphere = new GameObject("sphere");

    vector<Mesh*>* meshes = new vector<Mesh *>();
    meshes->push_back(createSphereMesh(radius, row, col));

    vector<Material*>* materials = new vector<Material*>();
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/sphere.mat");
    Material* copyMaterial = new Material(m);;
    m->drop();
    materials->push_back(copyMaterial);

    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);

    sphere->addComponent(meshfilter);
    sphere->addComponent(render);

    return sphere;
}


hxe::GameObject* hxe::Creator::createRobot(Color color)
{
    Mesh* sphereMesh = createSphereMesh(2.0f, 50, 50);
    Mesh* boxMesh = createCubeMesh(2.0f, 3.0f, 2.0f);
    vector<CombineInstance> combines(2);
    combines[0].mesh = sphereMesh;
    combines[0].transform = glm::translate(Matrix4x4(1.0f), Vector3(0.0f, 0.0f, 4.0f));

    combines[1].mesh = boxMesh;
    combines[1].transform = glm::translate(Matrix4x4(1.0f), Vector3(0.0f, 0.0f, -1.0f));
    Mesh* combineMesh = new Mesh(combines);
    sphereMesh->drop();
    boxMesh->drop();

    GameObject* robot = new GameObject("robot");

    vector<Mesh*>* meshes = new vector<Mesh *>();
    meshes->push_back(combineMesh);

    vector<Material*>* materials = new vector<Material *>();
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/sphere.mat");
    Material* copyMaterial = new Material(m);
    m->drop();
    copyMaterial->setVector4("color", color);
    materials->push_back(copyMaterial);

    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);

    robot->addComponent(meshfilter);
    robot->addComponent(render);

    return robot;

}


hxe::SkyBox* hxe::Creator::createSkyBox()
{
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/skyBox.mat");
    SkyBox* skyBox = new SkyBox(m);
    return skyBox;
}

hxe::Light* hxe::Creator::createLight(Vector3 position, Vector3 eularAngle,
                    Color lightColor, Color ambientColor)
{
    Light* light = new Light();
    GameObject* lightObject = Creator::createSphere(3.0f, 30, 30);
    lightObject->name = "light";
    lightObject->addComponent(light);

    Render* render = (Render*)lightObject->getComponent("Render");
    render->getMaterial()->setVector4("color", lightColor);

    light->lightColor = lightColor;
    light->ambientColor = ambientColor;
    light->gameObject->transform->position = position;
    light->gameObject->transform->setEulerAngles(eularAngle);
    return light;
}

hxe::GameObject* hxe::Creator::createModel(std::string path, Vector3 position, std::string name)
{
    GameObject* nanosuit = new GameObject(name);
    nanosuit->transform->position = position;
    
    vector<Mesh*>* meshes = new vector<Mesh *>();
    vector<Material*>* materials = new vector<Material *>();
    loadModel(meshes, materials, path);
    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);
    
    nanosuit->addComponent(meshfilter);
    nanosuit->addComponent(render);
    
    return nanosuit;
}

hxe::Camera* hxe::Creator::createCamera(Vector3 position, Vector3 forward, Vector3 up, float fov, float aspect, float near, float far)
{
    Camera* camera = new FirstPersonCamera(forward, up, fov, aspect, near, far);
    camera->gameObject->transform->position = position;
    return camera;
}


hxe::Camera* hxe::Creator::createUICamera(Vector3 position, Vector3 forward, Vector3 up, float orthographicSize, float aspect, float near, float far)
{
    Camera* camera = new Camera(orthographicSize, aspect, near, far);
    camera->depth = 1;
    camera->gameObject->transform->position = position;
    camera->gameObject->transform->forward = forward;
    camera->gameObject->transform->up = up;
    return camera;
}

hxe::Mesh* hxe::Creator::createPlaneMesh(float width, float height, int horizontalCount, int verticalCount)
{
    vector<Vector3>* vertices = new vector<Vector3>();
    vector<Vector3>* normals = new vector<Vector3>();
    vector<Vector2>* uv = new vector<Vector2>();
    vector<unsigned int>* indices = new vector<unsigned int>();

    float hStart = -(width * horizontalCount / 2.0f);
    float vStart = -(height * verticalCount / 2.0f);
    for (int i = 0; i <= horizontalCount; i++)
    {
        float x = hStart + i * width;
        for (int j = 0; j <= verticalCount; j++)
        {
            float z = vStart + j * height;
            Vector3 v;
            v.x = x;
            v.y = 0;
            v.z = z;
            vertices->push_back(v);

            Vector3 n = Vector3(0.0f, 1.0f, 0.0f);
            normals->push_back((n));

            uv->push_back(Vector2(0.0f, 0.0f));
        }
    }

    int colCount = verticalCount + 1;
    for (int i = 0; i < horizontalCount; i++)
    {
        for (int j = 0; j < verticalCount; j++)
        {
            indices->push_back(i * colCount + j);
            indices->push_back(i * colCount + j + 1);
            indices->push_back((i + 1) * colCount + j);

            indices->push_back((i + 1) * colCount + j);
            indices->push_back(i * colCount + j + 1);
            indices->push_back((i + 1) * colCount + j + 1);
        }
    }

    return new Mesh(vertices, normals, uv, indices);
}


hxe::GameObject* hxe::Creator::createPlane(float width, float height, int horizontalCount, int verticalCount)
{
    GameObject* plane = new GameObject("plane");

    vector<Mesh*>* meshes = new vector<Mesh *>();
    meshes->push_back(createPlaneMesh(width, height, horizontalCount, verticalCount));

    vector<Material*>* materials = new vector<Material *>();
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/plane.mat");
    materials->push_back(m);

    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);

    plane->addComponent(meshfilter);
    plane->addComponent(render);

    return plane;
}


hxe::Mesh* hxe::Creator::createCubeMesh(float length, float width, float height)
{
    length /= 2.0f;
    width /= 2.0f;
    height /= 2.0f;
    vector<Vector3>* vertices = new vector<Vector3>();
    vector<Vector3>* normals = new vector<Vector3>();
    vector<Vector2>* uv = new vector<Vector2>();
    vector<unsigned int>* indices = new vector<unsigned int>();

    // back
    vertices->push_back(Vector3(length, -height, -width));
    vertices->push_back(Vector3(-length, -height, -width));
    vertices->push_back(Vector3(-length, height, -width));
    vertices->push_back(Vector3(length, height, -width));

    normals->push_back(glm::normalize(Vector3(1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, 1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(1.0f, 1.0f, -1.0f)));

    uv->push_back(Vector2(0.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 0.0f));
    uv->push_back(Vector2(0.0f, 0.0f));

    // left
    vertices->push_back(Vector3(-length, -height, -width));
    vertices->push_back(Vector3(-length, -height, width));
    vertices->push_back(Vector3(-length, height, width));
    vertices->push_back(Vector3(-length, height, -width));

    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f,1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, 1.0f, 1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, 1.0f, -1.0f)));

    uv->push_back(Vector2(0.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 0.0f));
    uv->push_back(Vector2(0.0f, 0.0f));

    // bottom
    vertices->push_back(Vector3(-length, -height, -width));
    vertices->push_back(Vector3(length, -height, -width));
    vertices->push_back(Vector3(length, -height, width));
    vertices->push_back(Vector3(-length, -height, width));

    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(1.0f, -1.0f, 1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f, 1.0f)));

    uv->push_back(Vector2(0.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 0.0f));
    uv->push_back(Vector2(0.0f, 0.0f));

    for (unsigned int i = 0; i < 4; i++)
    {
        Vector3 v = (*vertices)[i];
        v.z = -(*vertices)[i].z;
        vertices->push_back(v);

        Vector3 n = (*normals)[i];
        n.z = -(*normals)[i].z;
        normals->push_back(n);
    }

    for (unsigned int i = 0; i < 4; i++)
    {
        unsigned int index = 4 + i;
        Vector3 v = (*vertices)[index];
        v.x = -(*vertices)[index].x;
        vertices->push_back(v);

        Vector3 n = (*normals)[index];
        n.x = -(*normals)[index].x;
        normals->push_back(n);
    }

    for (unsigned int i = 0; i < 4; i++)
    {
        unsigned int index = 8 + i;
        Vector3 v = (*vertices)[index];
        v.y = -(*vertices)[index].y;
        vertices->push_back(v);

        Vector3 n = (*normals)[index];
        n.y = -(*normals)[index].y;
        normals->push_back(n);
    }

    for (int i = 0; i < 3; i++)
    {
        uv->push_back(Vector2(0.0f, 1.0f));
        uv->push_back(Vector2(1.0f, 1.0f));
        uv->push_back(Vector2(1.0f, 0.0f));
        uv->push_back(Vector2(0.0f, 0.0f));
    }

    // faces
    for (unsigned int i = 0; i < 3; i++)
    {
        unsigned int f1 = i * 4;
        unsigned int f2 = i * 4 + 1;
        unsigned int f3 = i * 4 + 2;
        unsigned int f4 = i * 4 + 3;

        indices->push_back(f1);
        indices->push_back(f2);
        indices->push_back(f3);

        indices->push_back(f1);
        indices->push_back(f3);
        indices->push_back(f4);
    }

    for (unsigned int i = 0; i < 3; i++)
    {
        unsigned step = 12;
        unsigned int f1 = i * 4 + step;
        unsigned int f2 = i * 4 + 1 + step;
        unsigned int f3 = i * 4 + 2 + step;
        unsigned int f4 = i * 4 + 3 + step;

        indices->push_back(f3);
        indices->push_back(f2);
        indices->push_back(f1);

        indices->push_back(f4);
        indices->push_back(f3);
        indices->push_back(f1);
    }

    return new Mesh(vertices, normals, uv, indices);
}

hxe::Mesh* hxe::Creator::createImageMesh(float width, float height)
{
    width /= 2.0f;
    height /= 2.0f;
    vector<Vector3>* vertices = new vector<Vector3>();
    vector<Vector3>* normals = new vector<Vector3>();
    vector<Vector2>* uv = new vector<Vector2>();
    vector<unsigned int>* indices = new vector<unsigned int>();

    // back
    vertices->push_back(Vector3(width, -height, 0.0f));
    vertices->push_back(Vector3(-width, -height, 0.0f));
    vertices->push_back(Vector3(-width, height, 0.0f));
    vertices->push_back(Vector3(width, height, 0.0f));

    normals->push_back(glm::normalize(Vector3(1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, -1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(-1.0f, 1.0f, -1.0f)));
    normals->push_back(glm::normalize(Vector3(1.0f, 1.0f, -1.0f)));

    uv->push_back(Vector2(0.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 1.0f));
    uv->push_back(Vector2(1.0f, 0.0f));
    uv->push_back(Vector2(0.0f, 0.0f));

    // faces
    indices->push_back(0);
    indices->push_back(1);
    indices->push_back(2);

    indices->push_back(0);
    indices->push_back(2);
    indices->push_back(3);

    return new Mesh(vertices, normals, uv, indices);
}


hxe::GameObject* hxe::Creator::createCube(float length, float width, float height)
{
    GameObject* cube = new GameObject("cube");

    vector<Mesh*>* meshes = new vector<Mesh *>();
    meshes->push_back(createCubeMesh(length, width, height));

    vector<Material*>* materials = new vector<Material*>();
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/cube.mat");
    materials->push_back(m);

    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);

    cube->addComponent(meshfilter);
    cube->addComponent(render);

    return cube;
}

hxe::GameObject* hxe::Creator::createImage(const std::string& path, float width, float height)
{
    GameObject* image = new GameObject("image");

    vector<Mesh*>* meshes = new vector<Mesh *>();
    meshes->push_back(createImageMesh(width, height));

    vector<Material*>* materials = new vector<Material*>();
    Material* m = AssetDatabaseInst->loadAssetAtPath<Material>("Assets/Material/image.mat");
    materials->push_back(m);

    Texture* texture = AssetDatabaseInst->loadAssetAtPath<Texture>(path, "diffuseTexture");
    m->addTexture(texture);

    MeshFilter* meshfilter = new MeshFilter(meshes);
    Render* render = new Render(materials);

    image->addComponent(meshfilter);
    image->addComponent(render);

    return image;
}

void hxe::Creator::loadModel(vector<Mesh*>* meshes, vector<Material*>* materials, const std::string& path)
{
    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

    if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
    {
        std::string logStr = "loadModel failed path:";
        logStr += path;

        LoggerInst->print(logStr.c_str());
        return;
    }
    std::string directory = path.substr(0, path.find_last_of("/"));

    processNode(meshes, materials, scene->mRootNode, scene, directory);
}

void hxe::Creator::processNode(vector<Mesh*>* meshes, vector<Material*>* materials,
                                aiNode* node, const aiScene* scene,
                                 const std::string directory)
{
    for (unsigned int i = 0; i < node->mNumMeshes; i++)
    {
        aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
        if (mesh->mMaterialIndex >= 0)
        {
            Mesh* m = processMesh(mesh, scene);
            meshes->push_back(m);
            
            vector<Texture*> textures;
            aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
            vector<Texture*> diffuseTex = processMaterialTextures(material, aiTextureType_DIFFUSE, "diffuseTexture", directory);
            textures.insert(textures.end(), diffuseTex.begin(), diffuseTex.end());
            
            vector<Texture*> specularTex = processMaterialTextures(material, aiTextureType_SPECULAR, "specularTexture", directory);
            textures.insert(textures.end(), specularTex.begin(), specularTex.end());
            
            Shader* shader = AssetDatabaseInst->loadAssetAtPath<Shader>("Assets/Shader/common.vert", "Assets/Shader/specular.frag");
            Material* m2 = new Material(shader, textures);
            materials->push_back(m2);
        }

    }

    for (unsigned int i = 0; i < node->mNumChildren; i++)
    {
        processNode(meshes, materials, node->mChildren[i], scene, directory);
    }
}

hxe::Mesh* hxe::Creator::processMesh(aiMesh* mesh, const aiScene* scene)
{
    vector<Vector3>* vertices = new vector<Vector3>();
    vector<Vector3>* normals = new vector<Vector3>();
    vector<Vector2>* uv = new vector<Vector2>();
    vector<unsigned int>* indices = new vector<unsigned int>();

    vertices->resize(mesh->mNumVertices);
    normals->resize(mesh->mNumVertices);
    uv->resize(mesh->mNumVertices);
    for (unsigned int i = 0; i < mesh->mNumVertices; i++)
    {
        Vector3 v;
        v.x = mesh->mVertices[i].x;
        v.y = mesh->mVertices[i].y;
        v.z = mesh->mVertices[i].z;
        (*vertices)[i] = v;

        Vector3 n;
        n.x = mesh->mNormals[i].x;
        n.y = mesh->mNormals[i].y;
        n.z = mesh->mNormals[i].z;
        (*normals)[i] = n;

        Vector2 u = Vector2(0.0f, 0.0f);
        if (mesh->mTextureCoords[0])
        {
            u.x = mesh->mTextureCoords[0][i].x;
            u.y = mesh->mTextureCoords[0][i].y;
        }
        (*uv)[i] = u;
    }

    indices->resize(mesh->mNumFaces * 3);
    for (unsigned int i = 0; i < mesh->mNumFaces; i++)
    {
        aiFace face = mesh->mFaces[i];
        for (unsigned int j = 0; j < face.mNumIndices; j++)
        {
            (*indices)[i * 3 + j] = face.mIndices[j];
        }
    }

    return new Mesh(vertices, normals, uv, indices);
}

std::vector<hxe::Texture*> hxe::Creator::processMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName, const std::string directory)
{
    std::vector<Texture*> textures;
    for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
    {
        aiString str;
        mat->GetTexture(type, i, &str);
        Texture* texture = AssetDatabaseInst->loadAssetAtPath<Texture>(directory + "/" + str.C_Str(), typeName);
        textures.push_back(texture);
    }
    return textures;
}

void hxe::Creator::genNormals(vector<Vector3>* normals, const vector<Vector3>* vertices, const vector<unsigned int>* indices)
{
    normals->resize(vertices->size());

    for (unsigned int i = 0; i < vertices->size(); i++)
    {
        (*normals)[i] = Vector3(0.0f, 0.0f, 0.0f);
    }

    for (unsigned int i = 0; i < indices->size(); i += 3)
    {
        unsigned i1 = (*indices)[i];
        unsigned i2 = (*indices)[i + 1];
        unsigned i3 = (*indices)[i + 2];

        Vector3 v1 = (*vertices)[i2] - (*vertices)[i1];
        Vector3 v2 = (*vertices)[i3] - (*vertices)[i1];

        Vector3 n = glm::cross(v1, v2);
        for (unsigned int j = 0; j < 3; j++)
        {
            (*normals)[(*indices)[i + j]] = (*normals)[(*indices)[i + j]] + n;
        }
    }

    for (unsigned int i = 0; i < normals->size(); i++)
    {
        (*normals)[i] = glm::normalize((*normals)[i]);
    }
}
